/*
 * MIT License
 *
 * Copyright (c) 2023-present, tangli
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package tony.mybatis.sqlinjector.method

import com.baomidou.mybatisplus.annotation.FieldFill
import com.baomidou.mybatisplus.annotation.FieldStrategy
import com.baomidou.mybatisplus.core.injector.AbstractMethod
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo
import com.baomidou.mybatisplus.core.metadata.TableInfo
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator
import org.apache.ibatis.executor.keygen.KeyGenerator
import org.apache.ibatis.executor.keygen.NoKeyGenerator
import org.apache.ibatis.mapping.MappedStatement
import org.apache.ibatis.mapping.SqlCommandType

/**
 * 根据ID进行插入或更新操作（MySQL ON DUPLICATE KEY UPDATE）
 * 如果主键存在则更新，不存在则插入
 *
 * @author tangli
 * @date 2024/01/15 10:00
 */
internal class UpsertById : AbstractMethod("upsertById") {
    override fun injectMappedStatement(
        mapperClass: Class<*>,
        modelClass: Class<*>,
        tableInfo: TableInfo,
    ): MappedStatement {
        // 构建动态 SQL，使用 MyBatis 的动态标签以支持自动填充
        val sql = buildDynamicSql(tableInfo)

        val sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass)

        // 处理主键生成策略
        val keyGenerator = getKeyGenerator(tableInfo)
        val keyProperty = if (tableInfo.havePK()) tableInfo.keyProperty else null
        val keyColumn = if (tableInfo.havePK()) tableInfo.keyColumn else null

        // 使用 addInsertMappedStatement 来正确设置 SqlCommandType.INSERT，触发自动填充
        return addMappedStatement(
            mapperClass,
            methodName,
            sqlSource,
            SqlCommandType.UPDATE,
            modelClass,
            null,
            Int::class.java,
            keyGenerator,
            keyProperty,
            keyColumn
        )
    }

    /**
     * 构建动态 SQL，支持自动填充
     */
    private fun buildDynamicSql(tableInfo: TableInfo): String {
        val insertPart = buildInsertPart(tableInfo)
        val updatePart = buildUpdatePart(tableInfo)

        return "<script>\nINSERT INTO ${tableInfo.tableName} $insertPart ON DUPLICATE KEY UPDATE $updatePart\n</script>"
    }

    /**
     * 构建插入部分（包含列和值）
     */
    private fun buildInsertPart(tableInfo: TableInfo): String {
        val columnParts = mutableListOf<String>()
        val valueParts = mutableListOf<String>()

        // 在 upsert 操作中总是包含主键列，因为需要用于 ON DUPLICATE KEY UPDATE
        if (tableInfo.havePK()) {
            columnParts.add("${tableInfo.keyColumn},")
            valueParts.add("#{${tableInfo.keyProperty}},")
        }

        // 添加普通字段列和值，使用动态条件
        tableInfo.fieldList.forEach { field ->
            if (shouldIncludeFieldInInsert(field)) {
                // 对于自动填充字段，总是包含
                if (isInsertFillField(field)) {
                    columnParts.add("${field.column},")
                    valueParts.add("#{${field.property}},")
                } else if (field.fieldFill != FieldFill.UPDATE) {
                    // 对于非自动填充字段，根据策略使用条件判断
                    when (field.insertStrategy) {
                        FieldStrategy.NOT_NULL -> {
                            columnParts.add("""<if test="${field.property} != null">${field.column},</if>""")
                            valueParts.add("""<if test="${field.property} != null">#{${field.property}},</if>""")
                        }

                        FieldStrategy.NOT_EMPTY -> {
                            columnParts.add(
                                """<if test="${field.property} != null and ${field.property} != ''">${field.column},</if>"""
                            )
                            valueParts.add(
                                """<if test="${field.property} != null and ${field.property} != ''">#{${field.property}},</if>"""
                            )
                        }

                        else -> {
                            columnParts.add("${field.column},")
                            valueParts.add("#{${field.property}},")
                        }
                    }
                }
            }
        }

        return """
            <trim prefix="(" suffix=")" suffixOverrides=",">
             ${columnParts.joinToString("\n")}
            </trim>
            VALUES
            <trim prefix="(" suffix=")" suffixOverrides=",">
              ${valueParts.joinToString("\n")}
            </trim>
            """.trimIndent()
    }

    /**
     * 构建更新部分
     */
    private fun buildUpdatePart(tableInfo: TableInfo): String {
        val updateParts = mutableListOf<String>()

        // 普通字段的更新，使用动态条件
        tableInfo.fieldList.forEach { field ->
            if (shouldIncludeFieldInUpdate(field)) {
                // 对于自动填充字段，总是更新
                if (isUpdateFillField(field)) {
                    updateParts.add("${field.column} = #{${field.property}},")
                } else if (!field.update.isNullOrBlank()) {
                    updateParts.add("${field.column} = ${String.format(field.update, field.column)},")
                } else if (field.fieldFill != FieldFill.INSERT) {
                    // 对于非自动填充字段，根据策略使用条件判断
                    when (field.updateStrategy) {
                        FieldStrategy.NOT_NULL -> {
                            updateParts.add(
                                """<if test="${field.property} != null">${field.column} = #{${field.property}},</if>"""
                            )
                        }

                        FieldStrategy.NOT_EMPTY -> {
                            updateParts.add(
                                """<if test="${field.property} != null and ${field.property} != ''">${field.column} = #{${field.property}},</if>"""
                            )
                        }

                        else -> {
                            updateParts.add("${field.column} = #{${field.property}},")
                        }
                    }
                }
            }
        }

        return """
            <trim suffixOverrides=",">
              ${updateParts.joinToString("\n")}
            </trim>
            """.trimIndent()
    }

    /**
     * 判断是否为自动填充字段
     */
    private fun isAutoFillField(field: TableFieldInfo): Boolean =
        field.fieldFill != FieldFill.DEFAULT

    /**
     * 判断是否为自动填充字段
     */
    private fun isInsertFillField(field: TableFieldInfo): Boolean =
        field.fieldFill == FieldFill.INSERT || field.fieldFill == FieldFill.INSERT_UPDATE

    /**
     * 判断是否为自动填充字段
     */
    private fun isUpdateFillField(field: TableFieldInfo): Boolean =
        field.fieldFill == FieldFill.UPDATE || field.fieldFill == FieldFill.INSERT_UPDATE

    /**
     * 判断字段是否应该包含在插入中
     */
    private fun shouldIncludeFieldInInsert(field: TableFieldInfo): Boolean =
        field.insertStrategy != FieldStrategy.NEVER

    /**
     * 判断字段是否应该包含在更新中
     */
    private fun shouldIncludeFieldInUpdate(field: TableFieldInfo): Boolean {
        // 自动填充字段在UPDATE时包含
        if (field.fieldFill == FieldFill.UPDATE || field.fieldFill == FieldFill.INSERT_UPDATE) {
            return true
        }
        return field.updateStrategy != FieldStrategy.NEVER
    }

    /**
     * 判断是否为自增主键
     */
    private fun isAutoIncrement(tableInfo: TableInfo): Boolean =
        tableInfo.idType?.name == "AUTO"

    /**
     * 获取主键生成器
     */
    private fun getKeyGenerator(tableInfo: TableInfo): KeyGenerator =
        if (tableInfo.havePK() && isAutoIncrement(tableInfo)) {
            Jdbc3KeyGenerator()
        } else {
            NoKeyGenerator.INSTANCE
        }
}
